/*
Copyright IBM Corp. 2017 All Rights Reserved.

SPDX-License-Identifier: Apache-2.0
*/

package policy

import (
	"testing"

	"github.com/hyperledger/fabric-protos-go-apiv2/peer"
	"github.com/hyperledger/fabric/common/policies"
	"github.com/hyperledger/fabric/core/policy/mocks"
	"github.com/hyperledger/fabric/protoutil"
	"github.com/stretchr/testify/require"
)

func TestCheckPolicyInvalidArgs(t *testing.T) {
	policyManagerGetter := &mocks.MockChannelPolicyManagerGetter{
		Managers: map[string]policies.Manager{
			"A": &mocks.MockChannelPolicyManager{
				MockPolicy: &mocks.MockPolicy{
					Deserializer: &mocks.MockIdentityDeserializer{
						Identity: []byte("Alice"),
						Msg:      []byte("msg1"),
					},
				},
			},
		},
	}
	pc := &policyChecker{channelPolicyManagerGetter: policyManagerGetter}

	err := pc.CheckPolicy("B", "admin", &peer.SignedProposal{})
	require.Error(t, err)
	require.Contains(t, err.Error(), "Failed to get policy manager for channel [B]")
}

func TestCheckPolicyBySignedDataInvalidArgs(t *testing.T) {
	policyManagerGetter := &mocks.MockChannelPolicyManagerGetter{
		Managers: map[string]policies.Manager{
			"A": &mocks.MockChannelPolicyManager{
				MockPolicy: &mocks.MockPolicy{
					Deserializer: &mocks.MockIdentityDeserializer{
						Identity: []byte("Alice"),
						Msg:      []byte("msg1"),
					},
				},
			},
		},
	}
	pc := &policyChecker{channelPolicyManagerGetter: policyManagerGetter}

	err := pc.CheckPolicyBySignedData("", "admin", []*protoutil.SignedData{{}})
	require.Error(t, err)
	require.Contains(t, err.Error(), "Invalid channel ID name during check policy on signed data. Name must be different from nil.")

	err = pc.CheckPolicyBySignedData("A", "", []*protoutil.SignedData{{}})
	require.Error(t, err)
	require.Contains(t, err.Error(), "Invalid policy name during check policy on signed data on channel [A]. Name must be different from nil.")

	err = pc.CheckPolicyBySignedData("A", "admin", nil)
	require.Error(t, err)
	require.Contains(t, err.Error(), "Invalid signed data during check policy on channel [A] with policy [admin]")

	err = pc.CheckPolicyBySignedData("B", "admin", []*protoutil.SignedData{{}})
	require.Error(t, err)
	require.Contains(t, err.Error(), "Failed to get policy manager for channel [B]")

	err = pc.CheckPolicyBySignedData("A", "admin", []*protoutil.SignedData{{}})
	require.Error(t, err)
	require.Contains(t, err.Error(), "Failed evaluating policy on signed data during check policy on channel [A] with policy [admin]")
}

func TestPolicyCheckerInvalidArgs(t *testing.T) {
	policyManagerGetter := &mocks.MockChannelPolicyManagerGetter{
		Managers: map[string]policies.Manager{
			"A": &mocks.MockChannelPolicyManager{
				MockPolicy: &mocks.MockPolicy{Deserializer: &mocks.MockIdentityDeserializer{
					Identity: []byte("Alice"),
					Msg:      []byte("msg1"),
				}},
			},
			"B": &mocks.MockChannelPolicyManager{
				MockPolicy: &mocks.MockPolicy{Deserializer: &mocks.MockIdentityDeserializer{
					Identity: []byte("Bob"),
					Msg:      []byte("msg2"),
				}},
			},
			"C": &mocks.MockChannelPolicyManager{
				MockPolicy: &mocks.MockPolicy{Deserializer: &mocks.MockIdentityDeserializer{
					Identity: []byte("Alice"),
					Msg:      []byte("msg3"),
				}},
			},
		},
	}
	identityDeserializer := &mocks.MockIdentityDeserializer{
		Identity: []byte("Alice"),
		Msg:      []byte("msg1"),
	}
	pc := &policyChecker{
		channelPolicyManagerGetter: policyManagerGetter,
		localMSP:                   identityDeserializer,
		principalGetter:            &mocks.MockMSPPrincipalGetter{Principal: []byte("Alice")},
	}

	// Check that (non-empty channel, empty policy) fails
	err := pc.CheckPolicy("A", "", nil)
	require.Error(t, err)
	require.Contains(t, err.Error(), "Invalid policy name during check policy on channel [A]. Name must be different from nil.")

	// Check that (empty channel, empty policy) fails
	err = pc.CheckPolicy("", "", nil)
	require.Error(t, err)
	require.Contains(t, err.Error(), "Invalid policy name during channelless check policy. Name must be different from nil.")

	// Check that (non-empty channel, non-empty policy, nil proposal) fails
	err = pc.CheckPolicy("A", "A", nil)
	require.Error(t, err)
	require.Contains(t, err.Error(), "Invalid signed proposal during check policy on channel [A] with policy [A]")

	// Check that (empty channel, non-empty policy, nil proposal) fails
	err = pc.CheckPolicy("", "A", nil)
	require.Error(t, err)
	require.Contains(t, err.Error(), "Invalid signed proposal during channelless check policy with policy [A]")
}

func TestPolicyChecker(t *testing.T) {
	policyManagerGetter := &mocks.MockChannelPolicyManagerGetter{
		Managers: map[string]policies.Manager{
			"A": &mocks.MockChannelPolicyManager{
				MockPolicy: &mocks.MockPolicy{
					Deserializer: &mocks.MockIdentityDeserializer{Identity: []byte("Alice"), Msg: []byte("msg1")},
				},
			},
			"B": &mocks.MockChannelPolicyManager{
				MockPolicy: &mocks.MockPolicy{
					Deserializer: &mocks.MockIdentityDeserializer{
						Identity: []byte("Bob"),
						Msg:      []byte("msg2"),
					},
				},
			},
			"C": &mocks.MockChannelPolicyManager{
				MockPolicy: &mocks.MockPolicy{
					Deserializer: &mocks.MockIdentityDeserializer{
						Identity: []byte("Alice"),
						Msg:      []byte("msg3"),
					},
				},
			},
		},
	}
	identityDeserializer := &mocks.MockIdentityDeserializer{
		Identity: []byte("Alice"),
		Msg:      []byte("msg1"),
	}
	pc := &policyChecker{
		channelPolicyManagerGetter: policyManagerGetter,
		localMSP:                   identityDeserializer,
		principalGetter:            &mocks.MockMSPPrincipalGetter{Principal: []byte("Alice")},
	}

	t.Run("CheckPolicy", func(t *testing.T) {
		// Validate Alice signatures against channel A's readers
		sProp, _ := protoutil.MockSignedEndorserProposalOrPanic("A", &peer.ChaincodeSpec{}, []byte("Alice"), []byte("msg1"))
		policyManagerGetter.Managers["A"].(*mocks.MockChannelPolicyManager).MockPolicy.(*mocks.MockPolicy).Deserializer.(*mocks.MockIdentityDeserializer).Msg = sProp.ProposalBytes
		sProp.Signature = sProp.ProposalBytes
		err := pc.CheckPolicy("A", "readers", sProp)
		require.NoError(t, err)

		// Proposal from Alice for channel A should fail against channel B, where Alice is not involved
		err = pc.CheckPolicy("B", "readers", sProp)
		require.Error(t, err)
		require.Contains(t, err.Error(), "Failed evaluating policy on signed data during check policy on channel [B] with policy [readers]: [Invalid Identity]")

		// Proposal from Alice for channel A should fail against channel C, where Alice is involved but signature is not valid
		err = pc.CheckPolicy("C", "readers", sProp)
		require.Error(t, err)
		require.Contains(t, err.Error(), "Failed evaluating policy on signed data during check policy on channel [C] with policy [readers]: [Invalid Signature]")
	})

	t.Run("CheckPolicyNoChannel", func(t *testing.T) {
		sProp, _ := protoutil.MockSignedEndorserProposalOrPanic("A", &peer.ChaincodeSpec{}, []byte("Alice"), []byte("msg1"))
		sProp.Signature = sProp.ProposalBytes

		// Alice is a member of the local MSP, policy check must succeed
		identityDeserializer.Msg = sProp.ProposalBytes
		err := pc.CheckPolicyNoChannel(Members, sProp)
		require.NoError(t, err)

		sProp, _ = protoutil.MockSignedEndorserProposalOrPanic("A", &peer.ChaincodeSpec{}, []byte("Bob"), []byte("msg2"))
		// Bob is not a member of the local MSP, policy check must fail
		err = pc.CheckPolicyNoChannel(Members, sProp)
		require.Error(t, err)
		require.Contains(t, err.Error(), "Failed deserializing proposal creator during channelless check policy with policy [Members]: [Invalid Identity]")
	})

	t.Run("CheckPolicyNoChannel", func(t *testing.T) {
		signedData := &protoutil.SignedData{
			Identity:  []byte("Alice"),
			Data:      []byte("msg1"),
			Signature: []byte("msg1"), // for testing only, signature is same as data to pass MockIdentity.Verify
		}

		// Alice is a member of the local MSP, policy check must succeed
		identityDeserializer.Msg = signedData.Data
		err := pc.CheckPolicyNoChannelBySignedData(Admins, []*protoutil.SignedData{signedData})
		require.NoError(t, err)

		// CheckPolicyNoChannelBySignedData iterates each signed data and returns an error if any data is invalid
		// Bob is not a member of the local MSP, policy check must fail when deserializing the identity
		signedData2 := &protoutil.SignedData{
			Identity:  []byte("Bob"),
			Data:      []byte("msg2"),
			Signature: []byte("msg2"),
		}
		err = pc.CheckPolicyNoChannelBySignedData(Admins, []*protoutil.SignedData{signedData, signedData2})
		require.Error(t, err)
		require.Contains(t, err.Error(), "failed deserializing signed data identity during channelless check policy with policy [Admins]: [Invalid Identity]")

		// policy name cannot be empty
		err = pc.CheckPolicyNoChannelBySignedData("", []*protoutil.SignedData{signedData})
		require.EqualError(t, err, "invalid policy name during channelless check policy. Name must be different from nil.")

		// signed data cannot be nil or empty
		err = pc.CheckPolicyNoChannelBySignedData(Admins, nil)
		require.EqualError(t, err, "no signed data during channelless check policy with policy [Admins]")
		err = pc.CheckPolicyNoChannelBySignedData(Admins, []*protoutil.SignedData{})
		require.EqualError(t, err, "no signed data during channelless check policy with policy [Admins]")
	})
}
